/*
 *     This file is part of Mobile GPS Logger.
 *
 *     Mobile GPS Logger is free software: you can redistribute it and/or
 *     modify it under the terms of the GNU General Public License as
 *     published by the Free Software Foundation, either version 3 of the
 *     License, or (at your option) any later version.
 *
 *     Mobile GPS Logger is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License v3 for more details.
 *
 *     You should have received a copy of the GNU General Public License v3
 *     along with Mobile GPS Logger. If not, see <http://www.gnu.org/licenses/>.
 */

package cx.ath.skyflyer.gpslog.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * This reader reads from a given stream line by line. The lines this reader
 * returns do not contain any line separation characters.
 * @author Ahti Legonkov
 */
public class LineReader {

    public LineReader(InputStreamReader in, int bufferSize) {
        input = in;
        readStringBuffer = new char[bufferSize];
    }

    public LineReader(InputStreamReader in) {
        this(in, 1024);
    }

    public LineReader(InputStream in, int bufferSize) {
        input = new InputStreamReader(in);
        readStringBuffer = new char[bufferSize];
    }

    public LineReader(InputStream in) {
        this(in, 1024);
    }

    /**
     * Reads a single character from the stream.
     * @return a character read from the stream.
     * @throws java.io.IOException
     */
    public int read() throws IOException {
        if (input == null) {
            throw new IOException("The stream is closed.");
        }
        return input.read();
    }

    /**
     * Reads until the end of buffer or until end of line whichever comes
     * first.
     * @param buf a buffer to place the read characters into.
     * @return number of chars read into the buffer.
     * @throws java.io.IOException
     */
    public int read(char[] buf) throws IOException {
        return read(buf, 0, buf.length);
    }

    /**
     * Reads up to len bytes from the stream or until end of line whichever
     * comes first.
     * @param buf buffer to write data
     * @param offset offset the location to start writing in the buffer.
     * @param len maximum number of chars to read.
     * @return number of characters actuslly read.
     * @throws java.io.IOException
     */
    public int read(char[] buf, int offset, int len) throws IOException {

        int n = 0;

        int ch = read();
        while (ch == '\n' || ch == '\r') {
            ch = read();
        }
        
        for (int i = offset; i < len && i < buf.length; ++i) {
            if (ch == '\n' || ch == '\r')
                break;

            buf[offset + n] = (char)ch;
            ++n;
            ch = read();
        }
        return n;
    }

    private final char[] readStringBuffer;
    private int bytesAvailable = 0;
    private int readStringBufferOffset = 0;

    static int indexOf(char[] a, int ofs, char[] ch) {

        for (int i = ofs; i < a.length; ++i) {

            for (int j = 0; j < ch.length; ++j) {

                if (a[i] == ch[j])
                    return i;
            }
        }

        return -1;
    }

    private final char[] eolChars = new char[] { '\r', '\n' };

    /*public String readString() throws IOException {
        return readString(false);
    }*/

    /**
     *
     * @param blocking if true then this function is allowed to block.
     * @return
     * @throws java.io.IOException
     */
    public String readString(boolean blocking) throws IOException {
        StringBuffer res = new StringBuffer();

        // 1 - reading data characters
        // 2 - reading EOL characters
        int state = 1; // reading characters;

        while (true) {

            if (bytesAvailable == 0) {
                if (!input.ready() && !blocking)
                    return res.toString();

                bytesAvailable = input.read(readStringBuffer);
                readStringBufferOffset = 0;
            }

            switch (state) {
                case 1: // reading characters
                {
                    int idx = indexOf(readStringBuffer, readStringBufferOffset, eolChars);
                    if (idx == -1) {
                        int len = readStringBuffer.length - readStringBufferOffset;
                        res.append(readStringBuffer, readStringBufferOffset, len);
                        readStringBufferOffset = readStringBuffer.length;
                        bytesAvailable = 0;
                    }
                    else {
                        int len = idx - readStringBufferOffset;
                        res.append(readStringBuffer, readStringBufferOffset, len);
                        state = 2;
                        readStringBufferOffset = idx;
                        bytesAvailable -= len;
                    }

                    break;
                }

                case 2: // reading EOL
                {
                    int i = readStringBufferOffset;
                    while (i < readStringBuffer.length && (readStringBuffer[i] == '\r' || readStringBuffer[i] == '\n')) {
                        ++i;
                    }

                    bytesAvailable -= i - readStringBufferOffset;
                    readStringBufferOffset = i;
                    if (bytesAvailable > 0)
                        return res.toString();

                    break;
                }
            }
        }
    }

    /**
     * Closes the input stream. After calling this the read methods will fail.
     * @throws java.io.IOException
     */
    public void close() throws IOException {
        if (input != null) {
            input.close();
            input = null;
        }
    }

    private InputStreamReader input;

    private final char[] buf = new char[1024];

    public String readString() throws IOException {

        StringBuffer res = new StringBuffer();
        while (true) {
            int rd = read(buf);

            if (rd <= 0)
                break;

            res.append(new String(buf, 0, rd));

            if (rd < buf.length)
                break;
        }

        return res.toString();
    }
}